package org.candy.ioc;

import javafx.util.Pair;
import lombok.SneakyThrows;
import org.candy.ioc.annotation.IAutowired;
import org.candy.ioc.annotation.IComponent;
import org.candy.ioc.annotation.IComponentScan;
import org.candy.ioc.entity.BeanDefinition;
import org.candy.ioc.entity.Property;

import java.io.File;
import java.lang.reflect.Field;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

public class CandyApplicationContext {
    private ClassLoader classLoader;
    private List<BeanDefinition> beanDefinitions;
    private final Set<IComponentScan> configClassScans;
    private String scanPackagePath;
    private Map<String, Object> beans;

    // 1.扫描初始配置类，得到为Bean定义List
    // 2.循环Bean定义List，初始化Bean
    // 3.循环Bean定义List，autowired bean

    public CandyApplicationContext(Class<?> configClazz) {
        this.classLoader =  CandyApplicationContext.class.getClassLoader();
        this.beanDefinitions = new ArrayList<>();
        this.configClassScans = new HashSet<>();
        this.beans = new HashMap<>();

        this.scanApplicationConfig(configClazz);
        this.initBeans();
        this.autowiredBeans();
    }

    @SuppressWarnings("unchecked")
    public <T> T getBean(String beanName) {
        Object bean = beans.get(beanName);
        return (T) bean;
    }

    private void scanApplicationConfig(Class<?> applicationConfigClazz) {
        // 判断是否已经扫描或正在扫描，如果没有在扫描列表中，递归调用自己  0224
        // 0.把自己加入扫描列表
        // 1.获取扫描路径
        // 2.获取路径下所有class
        // 3.循环class
        //  3.1.如果是普通component，转换为definition
        //  3.2.如果是component scan，

        // 1. 获取配置类的扫描注解
        // 2. 如果配置类上有component注解，则将配置类加入beanDefinition集合
        // 3. 如果扫描列表中包括自己，则不再进行扫描
        // 4. 如果扫描列表中不包括，则加入扫描集合，以防扫描过程中再次遇到同一包的扫描(但可能遇到子包)
        // 5. 获取扫描路径
        // 6. 获取路径下所有class
        // 7. 循环class
        //  7.1. 如果是component scan，递归调用自己
        //  7.2. 如果是普通component，转换为definition
        //  7.3. 如果是切面的advice，则存储在集合中

        IComponentScan iComponentScan = applicationConfigClazz.getAnnotation(IComponentScan.class);
        if (Objects.isNull(iComponentScan)) {
            return;
        }

        if (configClassScans.contains(iComponentScan)) {
            return;
        }

        this.configClassScans.add(iComponentScan);

        String scanPath = iComponentScan.value();
        scanPath = scanPath.replace(".","/");

        List<Class<?>> classes = this.scan(scanPath);

        for (Class<?> clazz : classes) {
            if (Objects.nonNull(clazz.getAnnotation(IComponentScan.class))) {
                this.scanApplicationConfig(clazz);
            }
            if (Objects.nonNull(clazz.getAnnotation(IComponent.class))) {
                beanDefinitions.add(this.convertClassToDefinition(clazz));
            }
        }
    }

    @SneakyThrows
    private List<Class<?>> scan(String scanPath) {
        List<Class<?>> classList = new ArrayList<>();
        this.scanPackagePath = scanPath.replace("/", "\\");
        URL resource = classLoader.getResource(scanPath);

        if (Objects.isNull(resource)) {
            return classList;
        }

        String protocol = resource.getProtocol();
        if ("file".equals(protocol)) {
            File file = new File(resource.getFile());
            fileScan(file, classList);
        } else if ("jar".equals(protocol)) {
            JarFile jar = ((JarURLConnection) resource.openConnection()).getJarFile();
            jarScan(jar, scanPath, classList);
        }
        return classList;
    }

    @SneakyThrows
    private void fileScan(File file, List<Class<?>> classes) {
        File[] files = file.listFiles();
        if (Objects.nonNull(files)) {
            Arrays.stream(files).filter(File::isFile).forEach(f -> this.scanFileToClass(f, classes));
            Arrays.stream(files).filter(File::isDirectory).forEach(f -> this.fileScan(f, classes));
        }
    }

    private void jarScan(JarFile jar, String pkgName, List<Class<?>> classes) {

        Enumeration<JarEntry> entry = jar.entries();

        JarEntry jarEntry;
        String name, className;
        Class<?> claze;
        while (entry.hasMoreElements()) {
            jarEntry = entry.nextElement();

            name = jarEntry.getName();
            if (name.charAt(0) == '/') {
                name = name.substring(1);
            }
            if (jarEntry.isDirectory() || !name.startsWith(pkgName) || !name.endsWith(".class")) {
                // 非指定包路径， 非class文件
                continue;
            }
            // 去掉后面的".class", 将路径转为package格式
            className = name.substring(0, name.length() - 6);
            claze = loadClass(className.replace("/", "."));
            if (claze != null) {
                classes.add(claze);
            }
        }
    }

    @SneakyThrows
    private void scanFileToClass(File file, List<Class<?>> classes) {
        if (Objects.isNull(file)) {
            return;
        }

        String absolutePath = file.getAbsolutePath();
        if (absolutePath.endsWith(".class")) {
            absolutePath = absolutePath.substring(absolutePath.indexOf(this.scanPackagePath), absolutePath.indexOf(".class"));
        }
        absolutePath = absolutePath.replace(System.getProperty("file.separator"), ".");
        Class<?> clazz = classLoader.loadClass(absolutePath);
        classes.add(clazz);
    }

    private static Class<?> loadClass(String fullClzName) {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(fullClzName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private BeanDefinition convertClassToDefinition(Class<?> clazz) {

        BeanDefinition definition = new BeanDefinition();
        String id = clazz.getAnnotation(IComponent.class).value();
        definition.setId(id);
        definition.setClassType(clazz);
        Field[] fields = clazz.getDeclaredFields();
        List<Property> properties = new ArrayList<>();
        for (Field field : fields) {
            Property p = new Property();
            p.setName(field.getName());
            p.setType(field.getType());
            if (Objects.nonNull(field.getAnnotation(IAutowired.class))) {
                IAutowired iAutowired = field.getAnnotation(IAutowired.class);
                if (!iAutowired.value().equals("")) {
                    p.setRef(iAutowired.value());
                } else {
                    p.setRef("byClass");
                }
            }
            properties.add(p);
        }
        definition.setProperties(properties);
        return definition;
    }

    private void initBeans() {
        beans = this.beanDefinitions.stream()
                .map(definition -> new Pair<>(definition.getId(), definition.getClassType()))
                .collect(Collectors.toMap(Pair::getKey, definition -> this.newInstance(definition.getValue())));
    }

    private void autowiredBeans() {
        // 1.循环bean定义
        //  1.0.根据bean的name获取bean对象
        //  1.1.循环properties
        //  1.2.根据property的name获取属性对象
        //  1.2.将属性对象设置到bean对象
        this.beanDefinitions.stream()
                .flatMap(definition -> definition.getProperties()
                        .stream()
                        .map(property -> new Pair<>(this.beans.get(definition.getId()), property)))
                .filter(pair -> Objects.nonNull(pair.getValue().getRef()))
                .forEach(pair -> this.setAutowired(pair.getKey(), this.beans.get(pair.getValue().getRef()), pair.getValue().getName()));

    }

    private Object newInstance(Class<?> clazz) {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SneakyThrows
    private void setAutowired(Object bean, Object property, String name) {
        Field field = bean.getClass().getDeclaredField(name);
        field.setAccessible(true);

        //判断位置不对   0224
        if (Objects.isNull(property)) {
            Optional<?> object = this.beans.values().stream().filter(obj -> obj.getClass().equals(field.getType())).findFirst();
            if (object.isPresent()) {
                field.set(bean, object.get());
            } else {
                throw new RuntimeException("不存在该类型bean");
            }
        } else {
            field.set(bean, property);
        }
    }
}
